package org.springblade.core.oss;

import io.minio.*;
import io.minio.BucketExistsArgs.Builder;
import io.minio.errors.*;
import io.minio.messages.Item;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springblade.common.config.ArchiveOssRule;
import org.springblade.common.io.XorInputStream;
import org.springblade.common.io.XorOutputStream;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.oss.config.OssConfiguration;
import org.springblade.core.oss.enums.PolicyType;
import org.springblade.core.oss.model.OssFile;
import org.springblade.core.oss.props.OssProperties;
import org.springblade.core.secure.utils.AuthUtil;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CompletableFuture;

@Slf4j
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ArchiveMinioTemplate extends MinioTemplate implements OssTemplate {

	MinioClient minioClient;

	ArchiveOssRule archiveOssRule;


	public ArchiveMinioTemplate(MinioClient client, ArchiveOssRule archiveOssRule, OssProperties ossProperties) {
		super(client, archiveOssRule, ossProperties);
		this.archiveOssRule = archiveOssRule;
		this.minioClient = client;
	}

	public ArchiveMinioTemplate setTenantId(String tenantId) {
		if (!AuthUtil.getTenantId().equals(ArchiveOssRule.MANAGER_TENANT_ID) && !AuthUtil.getTenantId().equals(tenantId)) {
			//throw new ServiceException("非管理组，无法设置tenantId访问其他租户");
		}
		archiveOssRule.setTenantId(tenantId);
		return this;
	}

	private String getBucketName() {
		return archiveOssRule.bucketName();
	}

	private CompletableFuture<Void> putDir(File dir, File rootDir) {
		List<CompletableFuture<Void>> futures = new ArrayList<CompletableFuture<Void>>();
		for (File subFile : dir.listFiles()) {
			if (subFile.isDirectory()) {
				futures.add(putDir(subFile, rootDir));
			} else {
				String fileName = subFile.getAbsolutePath().replace(rootDir.getParentFile().getAbsolutePath(), "").replace("\\", "/");
				Runnable task = new Runnable() {
					@Override
					public void run() {

						try (InputStream inputStream = new XorInputStream(new FileInputStream(subFile))) {

							putFile(getBucketName(), fileName, inputStream);
							log.info(String.format("put file %s to oss success", fileName));
						} catch (IOException e) {
							e.printStackTrace();
							throw new RuntimeException(String.format("put file %s to oss fail,because %s", fileName, e.getMessage()));
						}
					}
				};
				CompletableFuture<Void> future = CompletableFuture.runAsync(task);
				futures.add(future);
			}
		}
		CompletableFuture<?>[] array = new CompletableFuture[futures.size()];
		futures.toArray(array);
		return CompletableFuture.allOf(array);
	}

	/*
	 * 重载解决第一次创建租户(多线程)时，提示创建桶错误
	 * */
	@Override
	public void makeBucket(String bucketName) {
		// TODO Auto-generated method stub
		try {
			synchronized (bucketName) {
				if (!this.minioClient.bucketExists(
					(BucketExistsArgs) ((Builder) BucketExistsArgs.builder().bucket(getBucketName()))
						.build())) {
					this.minioClient.makeBucket((MakeBucketArgs) ((MakeBucketArgs.Builder) MakeBucketArgs.builder()
						.bucket(getBucketName())).build());
					this.minioClient.setBucketPolicy(
						(SetBucketPolicyArgs) ((SetBucketPolicyArgs.Builder) SetBucketPolicyArgs.builder()
							.bucket(getBucketName()))
							.config(getPolicyType(getBucketName(), PolicyType.READ)).build());
				}
			}
		} catch (Throwable throwable) {

		}
	}

	/**
	 * minioTemplate statFile方法有bug，直接从配置文件读取bucketName，未走ossRule，故重载
	 */
	@Override
	public OssFile statFile(String fileName) {
		String bucketName = getBucketName();
		OssFile file = super.statFile(bucketName, fileName);
		file.setLink(this.fileLink(bucketName, fileName));
		return file;
	}

	@Override
	public String fileLink(String fileName) {
		log.debug(String.format("object name is %s,bucketName is %s", fileName, getBucketName()));
		return super.fileLink(getBucketName(), fileName);
	}


	@SneakyThrows
	public void uploadArchive(File archive) {
		log.info(String.format("start upload archive %s", archive.getName()));
		long startTime = System.currentTimeMillis();
		CompletableFuture<Void> future = putDir(archive, archive);
		future.whenComplete((r, e) -> {
			//完成上传后操作
			long endTime = System.currentTimeMillis();
			log.info(String.format("%s上传成功,用时%s", archive.getName(), endTime - startTime));
		});
		future.get();
	}


	@SneakyThrows
	public void downloadArchivePage(String objectName, OutputStream outputStream) {
		GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket(getBucketName()).object(objectName).build();
		try (InputStream inputStream = minioClient.getObject(getObjectArgs);) {
			IOUtils.copy(inputStream, outputStream);
		}
	}

	public void downloadObj(String objName, String objFile) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
		log.info("{},{},{}", getBucketName(), objName, objFile);
		minioClient.downloadObject(DownloadObjectArgs.builder()
			.bucket(getBucketName())
			.object(objName)
			.filename(objFile)
			.build());
	}

	@SneakyThrows
	public InputStream downloadArchivePage(String objectName) {
		GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket(getBucketName()).object(objectName).build();
		return minioClient.getObject(getObjectArgs);
	}


	@SneakyThrows
	public void uploadFile(InputStream stream, String minioPath) {
		log.info("upload file: {}", minioPath);
		minioClient.putObject(PutObjectArgs.builder().bucket(getBucketName()).object(minioPath).stream(stream, stream.available(), -1L).contentType("application/octet-stream").build());
	}


	@SneakyThrows
	public void uploadFileImage(InputStream stream, String minioPath) {
		log.info("upload image file: {}", minioPath);
		minioClient.putObject(PutObjectArgs.builder().bucket(getBucketName()).object(minioPath).stream(stream, stream.available(), -1L).contentType("image/jpeg").build());
	}

	@Async
	public void removeDir(String path) {

		log.info("remove dir: {}", path);

		String bucketName = getBucketName();
		if (!path.endsWith("/")) {
			path = path + "/";
		}
		// 列出该前缀下的所有对象
		Iterable<Result<Item>> results = minioClient.listObjects(
			ListObjectsArgs.builder()
				.bucket(bucketName)
				.prefix(path)
				.recursive(true) // 递归删除子目录
				.build()
		);
		for (Result<Item> result : results) {
			try {
				Item item = result.get();
				String objectName = item.objectName();
				minioClient.removeObject(
					RemoveObjectArgs.builder()
						.bucket(bucketName)
						.object(objectName)
						.build()
				);
				log.info("Deleted: " + objectName);
			}catch (Exception e){
				log.error("删除文件失败：{}",e.getMessage());
				log.error(e.getMessage(), e);
			}

		}

	}
}
